#include "bbh_logger.h"

#include "bbh_include.h"

#include "bbh_pattern.h"
#include "bbh_output.h"
#include "bbh_logger_utils.h"
#include "bbh_thread.h"
#include "bbh_linkitem.h"
#include "bbh_level.h"
#include "bbh_outbuffer.h"
#include "bbh_message.h"
#include "bbh_config.h"


#ifndef NULL
#define NULL (void*)0
#endif

//pthread_key_t key
static pthread_key_t    thread_key = 0;
static pthread_once_t   once = PTHREAD_ONCE_INIT;

typedef enum logger_thread_state {
    LOGGER_THREAD_NONE = 0,
    LOGGER_THREAD_RUN,
    LOGGER_THREAD_WAIT,
    LOGGER_THREAD_END
} logger_thread_state;

typedef struct log_msg_item
{
    char                    * msg;
    size_t                  msg_len;

    timeval                 tv;
    //
    bbh_linkitem_t          item;
    bbh_logger_t            * logger;
} log_msg_item;

struct bbh_logger_s
{
    // 可设置的属性
    char                    * file_outname;
    time_t                  file_last_createtime;
    int                     file_volume_count;

    bbh_config_t            * config;
    bbh_pattern_t           * pattern;//format;               // 输出格式
    ////////////////////////////////////////////
    bbh_output_t            * output;               // 不用释放
    void                    * handle;               // 

    // 数据队列
    pthread_mutex_t         msg_mutex;
    pthread_cond_t          msg_cond;
    bbh_linkitem_t          msg_root;
    // 
    pthread_t               thread_id;
    pthread_attr_t          attr;
    //
    logger_thread_state     thread_state;
    int64_t                 write_bytes;

    int                     rolling_count;
};

static log_msg_item* msg_item_create(size_t msg_len)
{
    log_msg_item*msg = (log_msg_item*)malloc(sizeof(log_msg_item) + msg_len);
    memset(msg, 0, sizeof(log_msg_item) + msg_len);
    msg->msg_len = msg_len;
    msg->msg = (char*)msg + sizeof(log_msg_item);
    bbh_linkitem_init(&msg->item);
    
    return msg;
}
static void msg_item_free(log_msg_item*item)
{
    bbh_linkitem_remove(&item->item);
    free(item);
}

static bbh_linkitem_t* take(bbh_logger_t*logger, bbh_linkitem_t*to)
{
    pthread_mutex_lock(&logger->msg_mutex);
    bbh_linkitem_move(&logger->msg_root, to);
    pthread_mutex_unlock(&logger->msg_mutex);
    return to;
}

inline static log_msg_item* pop(bbh_linkitem_t*root)
{
    log_msg_item*msg = NULL;

    if (!bbh_linkitem_isempty(root)) {
        bbh_linkitem_t*item = bbh_linkitem_popfront(root);
        msg = ADDRESS_OF(item, log_msg_item, item);
    }
    return msg;
}

/**
 * @brief 增加数据。同时判断是否要启动线程
 *          logger->thread_state==LOGGER_THREAD_NONE 并且 logger->msg_root为空时启动线程
 * 
 * @param logger 
 * @param item 
 */
static int push(bbh_logger_t*logger, log_msg_item*item)
{
    int ok = 0;

    item->logger = logger;

    pthread_mutex_lock(&logger->msg_mutex);
    if (logger->thread_state == LOGGER_THREAD_NONE) {
        if (bbh_linkitem_isempty(&logger->msg_root)) {
            ok = 1;
        }
    } else if (logger->thread_state == LOGGER_THREAD_WAIT) {
        pthread_cond_signal(&logger->msg_cond);
        ok = 0;
    }
    bbh_linkitem_pushback(&logger->msg_root, &item->item);
    pthread_mutex_unlock(&logger->msg_mutex);

    return ok;
}

/**
 * @brief 等待一段时间，返回是否有数据
 * @param _ 
 * @param timeout_ms 等待时间
 * @return int 是否有数据
 */
static int _logger_wait_cond(bbh_logger_t*_, long timeout_ms)
{
    int isempty = 0;
    //
    struct timespec abstime;
    struct timeval now;

    gettimeofday(&now, NULL);
    long nsec = now.tv_usec * 1000 + (timeout_ms % 1000) * 1000000;
    abstime.tv_sec=now.tv_sec + nsec / 1000000000 + timeout_ms / 1000;
    abstime.tv_nsec=nsec % 1000000000;
    //
    pthread_mutex_lock(&_->msg_mutex);
    _->thread_state = LOGGER_THREAD_WAIT;
    if (timeout_ms)
        pthread_cond_timedwait(&_->msg_cond, &_->msg_mutex, &abstime);
    else
        pthread_cond_wait(&_->msg_cond, &_->msg_mutex);

    isempty = bbh_linkitem_isempty(&_->msg_root);
    _->thread_state =  isempty ? LOGGER_THREAD_NONE : LOGGER_THREAD_RUN;
    pthread_mutex_unlock(&_->msg_mutex);

    return isempty ? 0 : 1;
}
static void _logger_set_thread_state(bbh_logger_t*_, logger_thread_state sta)
{
    pthread_mutex_lock(&_->msg_mutex);
    _->thread_state = sta;
    pthread_mutex_unlock(&_->msg_mutex);
}
static const char* _logger_make_output_filename(bbh_logger_t*_, time_t t)
{
    char    suffix[128];
    char    * buf = NULL;
    int     n;

    bbh_config_t * cfg = _->config;

    suffix[0] = 0;
    if (cfg->file_rolling_interval) {
        t = t - (t % cfg->file_rolling_interval);
        strftime(suffix, sizeof(suffix), "%Y%m%dT%H%M%S", localtime(&t));
        //printf("strftime:%ld, %s\n", t, suffix);
    }
    n = snprintf(buf, 0, "%s/%s%s%s.%s", 
                cfg->file_dir ? cfg->file_dir : ".",
                cfg->file_name,
                suffix[0] ? "_" : "",
                suffix,
                cfg->file_extname ? cfg->file_extname : "");
    buf = (char*)malloc(n + 1);

    n = snprintf(buf, n + 1, "%s/%s%s%s.%s", 
                cfg->file_dir ? cfg->file_dir : ".",
                cfg->file_name,
                suffix[0] ? "_" : "",
                suffix,
                cfg->file_extname ? cfg->file_extname : "");

    if (_->file_outname)
        free(_->file_outname);

    _->file_outname = buf;

    //printf("_logger_make_output_filename:%s\n", buf);
    return buf;
}

static void* _logger_thread_worker(void*data)
{
    printf("_logger_thread_worker::start\n");

    int         r = 0;

    bbh_logger_t* logger = (bbh_logger_t*)data;
    bbh_config_t*cfg = logger->config;

    // 句柄
    void        *handle = NULL;

    logger->file_last_createtime = 0;
    switch(logger->output->type)
    {
    case output_type_file:
        if (!cfg->file_rolling_interval) {
            _logger_make_output_filename(logger, time(NULL));
            r = logger->output->open(logger->file_outname, 0, &handle);
            if (r < 0) {
                printf("_logger_thread_worker::output_open 失败, r:%d,handle:%p\n",r,handle);
                pthread_exit(NULL);
                return NULL;
            }
            logger->file_last_createtime = time(NULL);
        }
        break;
    case output_type_stdout:
        r = logger->output->open(logger->file_outname, 0, &handle);
        if (r < 0) {
            printf("_logger_thread_worker::output_open 失败, r:%d,handle:%p\n",r,handle);
            pthread_exit(NULL);
            return NULL;
        }
        break;
    }
    // 保存句柄
    logger->handle = handle;

    // 设置运行状态
    _logger_set_thread_state(logger, LOGGER_THREAD_RUN);

    log_msg_item*item = NULL;
    bbh_linkitem_t root;
    bbh_linkitem_init(&root);
    do {
        int ccc = 0;
        //int64_t now = getSysTimeMicros();

        take(logger, &root);

        while(!bbh_linkitem_isempty(&root)) {

            while((item = pop(&root))) {

                ccc++;
                // 文件模式，检查是否要换文件名
                switch(logger->output->type) {
                case output_type_file:
                    do {
                        // 配置有回档时间段
                        if (cfg->file_rolling_interval) {
                            if ((logger->file_last_createtime / cfg->file_rolling_interval) != (item->tv.tv_sec / cfg->file_rolling_interval)) {
                                if (handle) {
                                    logger->output->close(handle);
                                }
                                // 清理文件
                                if (cfg->file_rolling_keep_num) {
                                    bbh_logger_rolling_file(cfg->file_dir,
                                        cfg->file_name,
                                        cfg->file_extname,
                                        item->tv.tv_sec - (item->tv.tv_sec % cfg->file_rolling_interval),
                                        cfg->file_rolling_interval,
                                        cfg->file_rolling_keep_num);
                                }

                                _logger_make_output_filename(logger, item->tv.tv_sec);
                                r = logger->output->open(logger->file_outname, 0, &handle);
                                logger->handle = handle;
                                logger->file_last_createtime = item->tv.tv_sec;
                            }
                        }
                    } while(0);
                    break;
                }

                // 输出记录
                r = logger->output->write(handle, item->msg, item->msg_len);

                logger->write_bytes += r;
                msg_item_free(item);
            }

            take(logger, &root);
        }
        
        //printf("before:%d, runtime:%ld\n", ccc, getSysTimeMicros() - now);
        logger->output->flush(handle);
        //printf("count:%d, runtime:%ld\n", ccc, getSysTimeMicros() - now);

        // 等十秒没有数据就退出
        if (cfg->free_time_ms && !_logger_wait_cond(logger, cfg->free_time_ms)) {
            printf("_logger_thread_worker::没有数据写了,退出 %d\n", cfg->free_time_ms);
            break;
        }
    } while(1);

    // 释放句柄
    if (handle) {
        logger->output->close(handle);
    }
    printf("_logger_thread_worker::end\n");
    
    pthread_exit(NULL);
}

static int  _logger_start(bbh_logger_t*_)
{
    return pthread_create(&_->thread_id, &_->attr, _logger_thread_worker,   (void *)_);
}

void init_message(bbh_message_t*msg, const char*filename, size_t filename_len, 
                        const char*function, size_t function_len, 
                        int line, int level)
{
    msg->filename = filename;
    msg->filename_len = filename_len;
    msg->function = function;
    msg->function_len = function_len;
    msg->shortname = strrchr(msg->filename, '/');
    if (msg->shortname) {
        msg->shortname ++;
        msg->shortname_len = msg->filename_len - (msg->shortname - msg->filename);
    } else {
        msg->shortname = msg->filename;
        msg->shortname_len = msg->filename_len;
    }
    msg->thread_id = pthread_self();
    msg->level = level;
    msg->line = line;
    if (msg->level > BBH_LEVEL_FATAL)
        msg->level = BBH_LEVEL_FATAL;
    if (msg->level < BBH_LEVEL_DEBUG)
        msg->level = BBH_LEVEL_DEBUG;

    gettimeofday(&msg->tv, 0);
}


////////////////////////////////////////
///
	// /* the 1st time in the whole process do init */
	// if (zlog_env_init_version == 0) {
	// 	/* clean up is done by OS when a thread call pthread_exit */
	// 	rc = pthread_key_create(&zlog_thread_key, (void (*) (void *)) zlog_thread_del);
	// 	if (rc) {
	// 		zc_error("pthread_key_create fail, rc[%d]", rc);
	// 		goto err;
	// 	}

	// 	/* if some thread do not call pthread_exit, like main thread
	// 	 * atexit will clean it 
	// 	 */
	// 	rc = atexit(zlog_clean_rest_thread);
	// 	if (rc) {
	// 		zc_error("atexit fail, rc[%d]", rc);
	// 		goto err;
	// 	}
	// 	zlog_env_init_version++;
	// } /* else maybe after zlog_fini() and need not create pthread_key */

////////////////////////////////////////
void  bbh_logger_init()
{
    /*int r = */
    pthread_key_create(&thread_key, (void (*) (void *))bbh_thread_free);
    //return r;
}
int  bbh_logger_create(const char*filepath, bbh_logger_t**pptr)
{
    pthread_once(&once,&bbh_logger_init);

    if (!filepath || !(*filepath)) {
        return -1;
    }

    struct bbh_logger_s*p = (struct bbh_logger_s*)malloc(sizeof(struct bbh_logger_s));

    memset(p, 0, sizeof(*p));

    bbh_linkitem_init(&p->msg_root);
    pthread_mutex_init(&p->msg_mutex, NULL);
    pthread_cond_init (&p->msg_cond, NULL);
    //
    pthread_attr_init(&p->attr);
    pthread_attr_setdetachstate(&p->attr, PTHREAD_CREATE_DETACHED);//PTHREAD_CREATE_JOINABLE);

    bbh_config_create(&p->config);
    bbh_config_init(p->config);

    p->output = bbh_output_init(filepath);
    if (p->output->type == output_type_file) {
        bbh_config_set_filepath(p->config, filepath);
    }

    *pptr = p;
    return 0;
}
int  bbh_logger_configure(bbh_logger_t*_, const char*key, const char*value)
{
    return bbh_config_set(_->config, key, value);
}
int  bbh_logger_configure_enable(bbh_logger_t*_)
{
    int r = 0;
    if (_->pattern) {
        return -1;
    }
    
    r = bbh_pattern_create((void**)&_->pattern);
    if (r < 0)
        return r;
        
    r = bbh_pattern_parse(_->pattern, _->config->pattern ? _->config->pattern : "%d() %m%n");
    if (r < 0) {
        bbh_pattern_free(_->pattern);
        _->pattern = NULL;
    }

    return r;
}
int  bbh_logger_write(bbh_logger_t*_, 
                        const char*filename, size_t filename_len, 
                        const char*function, size_t function_len, 
                        int line, int level, const char*s, size_t len)
{
    if (!_->pattern) {
        return -1;
    }
    if (level < _->config->level) {
        return -1;
    }

    bbh_thread_t*thd = bbh_thread_tsd(thread_key);

    int r;

    bbh_message_t msg;

    init_message(&msg, filename, filename_len, function, function_len, line, level);

    r = bbh_message_write(_->pattern, thd, &msg, s, len);
    log_msg_item*item = msg_item_create(thd->msg_buf->data_len);
    item->tv = msg.tv;
    memcpy(item->msg, thd->msg_buf->buf, item->msg_len);
    
    if (push(_, item)) {
        r = _logger_start(_);
    }

    return r;
}

int  bbh_logger_vprint(bbh_logger_t*_, 
                        const char*filename, size_t filename_len, 
                        const char*function, size_t function_len, 
                        int line, int level, const char*fmt, va_list args)
{
    if (!_->pattern) {
        return -1;
    }
    if (level < _->config->level) {
        return -1;
    }

    int r;
    bbh_thread_t*thd = bbh_thread_tsd(thread_key);

    va_list ap;
    va_copy(ap, args);

    bbh_message_t msg;

    init_message(&msg, filename, filename_len, function, function_len, line, level);

    r = bbh_message_vprint(_->pattern, thd, &msg, fmt, ap);//args);
    va_end(ap);

    log_msg_item*item = msg_item_create(thd->msg_buf->data_len);
    item->tv = msg.tv;
    memcpy(item->msg, thd->msg_buf->buf, item->msg_len);

    if (push(_, item)) {
        r = _logger_start(_);
    }


    return r;
}
int  bbh_logger_print(bbh_logger_t*_, 
                        const char*filename, size_t filename_len, 
                        const char*function, size_t function_len, 
                        int line, int level, const char*fmt, ...)
{
    if (!_->pattern) {
        return -1;
    }
    if (level < _->config->level) {
        return -1;
    }

    int r;
    va_list ap;
    va_start(ap, fmt);
    r = bbh_logger_vprint(_, filename, filename_len, function, function_len, line, level, fmt, ap);
    va_end(ap);

    return r;
}
void bbh_logger_free(bbh_logger_t*_)
{
    pthread_mutex_destroy(&_->msg_mutex);
    pthread_attr_destroy(&_->attr);
    pthread_cond_destroy(&_->msg_cond);

    //free(_->file_path);
    free(_->file_outname);
    bbh_config_free(_->config);
    bbh_pattern_free(_->pattern);
    free(_);
}
